import os
import tkinter as tk
from datetime import datetime
from tkinter import filedialog, messagebox

from PIL import Image, ImageTk
from PIL.Image import Resampling
from tkinterdnd2 import DND_FILES

from controllers.image_processor_controller import ImageProcessorController
from models.image_processor_model import ImageProcessorModel


class ImageProcessorView:
    """工作图片整理工具视图 - 支持内嵌到主界面"""

    def __init__(self, parent, config_manager, container=None):
        """
        初始化图片处理视图
        :param parent: 父窗口
        :param config_manager: 配置管理器实例
        :param container: 容器（用于内嵌模式）
        """
        # 初始化MVC组件
        self.model = ImageProcessorModel(config_manager)
        self.controller = ImageProcessorController(self.model)

        self.parent = parent
        self.frame = None

        # 当前已渲染的文件路径列表（用于增量更新）
        self._current_rendered_files = []
        self._scroll_initialized = False

        # 如果是内嵌模式，直接使用提供的容器
        if container:
            self.frame = container
            self.create_widgets(self.frame)
        else:
            # 独立窗口模式
            self.top = tk.Toplevel(parent)
            self.top.title("工作图片整理工具")
            self.top.geometry("800x650")
            self.create_widgets(self.top)

    def create_widgets(self, parent):
        """创建界面控件"""
        # 创建UI变量
        self.work_dir = tk.StringVar(value=self.model.config["work_dir"])
        self.folder_name = tk.StringVar(value=self.model.config["folder_name"])
        self.template_path = tk.StringVar(value=self.model.config["template_path"])
        self.status_text = tk.StringVar(value="就绪")

        main_frame = tk.Frame(parent)
        main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)

        # 工作路径选择
        tk.Label(main_frame, text="台账根目录:").grid(row=0, column=0, padx=10, pady=5, sticky="w")
        tk.Entry(main_frame, textvariable=self.work_dir, width=50).grid(row=0, column=1, padx=5, pady=5)
        tk.Button(main_frame, text="浏览...", command=self.browse_work_dir).grid(row=0, column=2, padx=5)

        # 文件夹命名
        tk.Label(main_frame, text="台账文件夹命名:").grid(row=3, column=0, padx=10, pady=5, sticky="w")
        tk.Entry(main_frame, textvariable=self.folder_name, width=50).grid(row=3, column=1, padx=5, pady=5)

        # Word模板选择
        tk.Label(main_frame, text="Word模板文件:").grid(row=4, column=0, padx=10, pady=5, sticky="w")
        tk.Entry(main_frame, textvariable=self.template_path, width=50).grid(row=4, column=1, padx=5, pady=5)
        tk.Button(main_frame, text="浏览...", command=self.browse_template).grid(row=4, column=2, padx=5)

        # 示例命名
        today = datetime.now().strftime("%Y年%m月%d日")
        tk.Label(main_frame, text=f"示例命名格式: {today}我局xxx带领xx对xxx进行xxxx检查", fg="gray").grid(
            row=6, column=1, padx=10, pady=2, sticky="w"
        )

        # 拖放区域框架
        self.drop_frame = tk.LabelFrame(main_frame, text="拖放图片区域 (或点击选择图片)", padx=10, pady=10)
        self.drop_frame.grid(row=7, column=0, columnspan=3, padx=10, pady=10, sticky="nsew")
        main_frame.grid_rowconfigure(7, weight=1)
        main_frame.grid_columnconfigure(1, weight=1)

        # 创建拖放区域
        self.drop_area = tk.Canvas(self.drop_frame, bg="#f0f0f0", bd=2, relief=tk.SUNKEN)
        self.drop_area.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)

        self.drop_text = self.drop_area.create_text(
            200, 100, text="拖放图片到此处\n或点击此处选择图片", fill="gray", font=("Arial", 12), justify="center"
        )

        # 预览容器
        self.preview_container = tk.Frame(self.drop_area)
        self.preview_window = self.drop_area.create_window(0, 0, anchor="nw", window=self.preview_container)

        # 配置拖放事件
        self.drop_area.drop_target_register(DND_FILES)
        self.drop_area.dnd_bind("<<DropEnter>>", self.configure_drop)
        self.drop_area.dnd_bind("<<DropLeave>>", self.deconfigure_drop)
        self.drop_area.dnd_bind("<<Drop>>", self.handle_drop)
        self.drop_area.bind("<Button-1>", self.select_images)

        # 初始化滚动结构（首次调用 update_preview 时构建）
        self.update_preview()

        # 执行按钮
        tk.Button(
            main_frame,
            text="开始处理",
            command=self.process_files,
            bg="#4CAF50",
            fg="white",
            height=2,
            width=20,
        ).grid(row=8, column=1, pady=10)

        # 状态标签
        self.status_label = tk.Label(main_frame, textvariable=self.status_text, fg="green")
        self.status_label.grid(row=9, column=1, pady=5)

    def browse_work_dir(self):
        directory = filedialog.askdirectory()
        if directory:
            self.work_dir.set(directory)

    def browse_template(self):
        file_path = filedialog.askopenfilename(filetypes=[("Word文件", "*.docx")])
        if file_path:
            self.template_path.set(file_path)

    def configure_drop(self, event):
        self.drop_area.configure(bg="#e0e0ff")

    def deconfigure_drop(self, event):
        self.drop_area.configure(bg="#f0f0f0")

    def handle_drop(self, event):
        files = self.drop_area.tk.splitlist(event.data)
        valid_files = [f for f in files if f.lower().endswith((".png", ".jpg", ".jpeg", ".bmp", ".heic"))]

        if valid_files:
            # 显示处理中的提示
            self.status_text.set("正在压缩图片...")
            self.frame.update()

            compressed_files = self.controller.compress_images_batch(valid_files)
            self.model.dropped_files.extend(compressed_files)

            self.status_text.set("就绪")
            self.update_preview()
        else:
            messagebox.showwarning("不支持的文件", "请拖放图片文件（支持.png, .jpg, .jpeg, .bmp, .heic）")

        self.deconfigure_drop(None)

    def select_images(self, event=None):
        files = filedialog.askopenfilenames(
            title="选择图片",
            filetypes=(("图片文件", "*.png;*.jpg;*.jpeg;*.bmp;*.heic"), ("所有文件", "*.*")),
        )
        if files:
            # 显示处理中的提示
            self.status_text.set("正在压缩图片...")
            self.frame.update()

            compressed_files = self.controller.compress_images_batch(list(files))
            self.model.dropped_files.extend(compressed_files)

            self.status_text.set("就绪")
            self.update_preview()

    def create_preview_item(self, idx, file_path, scrollable_frame):
        """异步创建单个预览项 - 使用已压缩的图片"""

        start_time = datetime.now()

        def update_placeholder(text):
            """在主线程中更新占位符文本"""
            if file_path not in self.model.dropped_files:
                return

            for child in scrollable_frame.winfo_children():
                if hasattr(child, "_file_path") and child._file_path == file_path:
                    child.config(text=text)
                    break

        def load_and_update():
            try:
                if file_path not in self.model.dropped_files:
                    return

                # 获取原始文件名用于显示
                original_path = self.model.get_original_path(file_path)
                display_name = os.path.basename(original_path)

                cache_path = self.model.get_thumbnail_path(file_path)

                # 更新状态：检查缓存
                self.frame.after(0, lambda: update_placeholder(f"⏳⏳⏳ {idx + 1}. 检查缓存..."))

                if file_path not in self.model.dropped_files:
                    return

                if os.path.exists(cache_path):
                    with Image.open(cache_path) as img:
                        photo = ImageTk.PhotoImage(img)
                        elapsed = (datetime.now() - start_time).total_seconds()
                        final_text = f"✅ {idx + 1}. {display_name} (缓存, {elapsed:.1f}s)"
                        self.frame.after(0, lambda: update_placeholder(final_text))
                        self.frame.after(0, lambda: self._render_preview_item(idx, file_path, photo, scrollable_frame,
                                                                              display_name))
                    return

                # 生成缩略图（从已压缩的图片）
                self.frame.after(0, lambda: update_placeholder(f"⏳⏳⏳ {idx + 1}. 生成缩略图..."))

                if file_path not in self.model.dropped_files:
                    return

                with Image.open(file_path) as img:
                    # 直接生成缩略图，不需要再次压缩
                    img.thumbnail((60, 60), Resampling.LANCZOS)

                    self.frame.after(0, lambda: update_placeholder(f"⏳⏳⏳ {idx + 1}. 缓存写入..."))
                    img.save(cache_path, "JPEG", quality=85, optimize=True)

                    photo = ImageTk.PhotoImage(img)
                    elapsed = (datetime.now() - start_time).total_seconds()
                    final_text = f"✅ {idx + 1}. {display_name} ({elapsed:.1f}s)"
                    self.frame.after(0, lambda: update_placeholder(final_text))
                    if file_path in self.model.dropped_files:
                        self.frame.after(0, lambda: self._render_preview_item(idx, file_path, photo, scrollable_frame,
                                                                              display_name))

            except Exception as e:
                elapsed = (datetime.now() - start_time).total_seconds()
                error_text = f"❌❌ {idx + 1}. 加载失败 ({elapsed:.1f}s)"
                self.frame.after(0, lambda: update_placeholder(error_text))
                if file_path in self.model.dropped_files:
                    self.frame.after(0, lambda: self._render_preview_error(idx, file_path, str(e), scrollable_frame))

        future = self.model.executor.submit(load_and_update)
        self.model.preview_futures.append(future)

    def _render_preview_item(self, idx, file_path, photo, parent_frame, display_name=None):
        """在主线程中渲染预览项"""
        if file_path not in self.model.dropped_files:
            return

        if idx >= len(self.model.dropped_files) or self.model.dropped_files[idx] != file_path:
            return

        # 精准查找并销毁对应"加载中"占位符
        for child in parent_frame.winfo_children():
            if hasattr(child, "_file_path") and child._file_path == file_path:
                child.destroy()
                break

        item_frame = tk.Frame(parent_frame, bd=1, relief=tk.RAISED, padx=5, pady=5)
        item_frame.pack(fill=tk.X, padx=5, pady=2)

        # 重要：给渲染的项目也添加文件路径标记
        item_frame._file_path = file_path

        grid_frame = tk.Frame(item_frame)
        grid_frame.pack(fill=tk.X)
        grid_frame.grid_columnconfigure(0, weight=0, minsize=70)
        grid_frame.grid_columnconfigure(1, weight=1)

        # 图片列
        img_frame = tk.Frame(grid_frame)
        img_frame.grid(row=0, column=0, sticky="ns")
        label_img = tk.Label(img_frame, image=photo)
        label_img.pack(pady=5)
        label_img.image = photo  # 防止被GC
        self.model.preview_images.append(photo)

        # 信息列
        info_frame = tk.Frame(grid_frame)
        info_frame.grid(row=0, column=1, sticky="nsew", padx=5)
        info_frame.grid_columnconfigure(0, weight=1)

        # 使用原始文件名显示
        if not display_name:
            display_name = os.path.basename(self.model.get_original_path(file_path))

        name_label = tk.Label(info_frame, text=f"{idx + 1}. {display_name}", anchor="w", justify="left", wraplength=300)
        name_label.grid(row=0, column=0, sticky="w")

        size = os.path.getsize(file_path)
        size_str = f"{size / 1024:.1f} KB" if size < 1024 * 1024 else f"{size / (1024 * 1024):.1f} MB"
        size_label = tk.Label(info_frame, text=f"大小: {size_str} (已压缩)", anchor="w", justify="left", fg="gray")
        size_label.grid(row=1, column=0, sticky="w")

        del_btn = tk.Button(
            item_frame,
            text="×",
            command=lambda fp=file_path: self.remove_file_by_path(fp),
            width=2, height=1, bg="#ff6666", fg="white", bd=0, relief=tk.FLAT,
            font=("Arial", 10, "bold"), cursor="hand2"
        )
        del_btn.place(relx=0.98, rely=0.02, anchor="ne")

    def _render_preview_error(self, idx, file_path, error_msg, parent_frame):
        """渲染错误项"""
        if file_path not in self.model.dropped_files:
            return

        if idx >= len(self.model.dropped_files) or self.model.dropped_files[idx] != file_path:
            return

        # 精准查找并销毁对应"加载中"占位符
        for child in parent_frame.winfo_children():
            if hasattr(child, "_file_path") and child._file_path == file_path:
                child.destroy()
                break

        item_frame = tk.Frame(parent_frame, bd=1, relief=tk.RAISED, padx=5, pady=5)
        item_frame.pack(fill=tk.X, padx=5, pady=2)

        # 重要：给错误项也添加文件路径标记
        item_frame._file_path = file_path

        error_label = tk.Label(item_frame, text=f"❌❌ 加载失败: {os.path.basename(file_path)}\n{error_msg}", fg="red",
                               wraplength=400)
        error_label.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

        del_btn = tk.Button(
            item_frame,
            text="×",
            command=lambda fp=file_path: self.remove_file_by_path(fp),
            width=2, height=1, bg="#ff6666", fg="white", bd=0, relief=tk.FLAT,
            font=("Arial", 10, "bold"), cursor="hand2"
        )
        del_btn.pack(side=tk.RIGHT, padx=5, pady=5)

    def _init_scroll_structure(self):
        """初始化滚动容器结构（只执行一次）"""
        for widget in self.preview_container.winfo_children():
            widget.destroy()

        main_frame = tk.Frame(self.preview_container)
        main_frame.pack(fill=tk.BOTH, expand=True)

        scroll_frame = tk.Frame(main_frame)
        scroll_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)

        scrollbar = tk.Scrollbar(scroll_frame)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)

        self.canvas = tk.Canvas(scroll_frame, yscrollcommand=scrollbar.set, highlightthickness=0)
        self.canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        scrollbar.config(command=self.canvas.yview)

        self.scrollable_frame_inner = tk.Frame(self.canvas)
        self.canvas.create_window((0, 0), window=self.scrollable_frame_inner, anchor="nw")

        def update_scrollregion(event=None):
            self.canvas.config(scrollregion=self.canvas.bbox("all"))

        self.scrollable_frame_inner.bind("<Configure>", update_scrollregion)

        # 清空按钮区
        self.clear_btn_frame = tk.Frame(main_frame)
        self.clear_btn_frame.pack(fill=tk.X, padx=5, pady=10)

        clear_btn = tk.Button(self.clear_btn_frame, text="清空所有图片", command=self.clear_all_files, fg="red",
                              width=20, height=1)
        clear_btn.pack(side=tk.BOTTOM, fill=tk.X)

    def update_preview(self):
        """更新预览区域"""
        # 取消所有待处理的异步任务
        for future in self.model.preview_futures:
            if not future.done():
                future.cancel()
        self.model.preview_futures.clear()

        # 清除旧预览引用
        self.model.preview_images.clear()

        if not self.model.dropped_files:
            self.drop_area.itemconfig(self.drop_text, state=tk.NORMAL)
            # 销毁滚动结构
            for widget in self.preview_container.winfo_children():
                widget.destroy()
            self._scroll_initialized = False
            self._current_rendered_files = []
            return

        self.drop_area.itemconfig(self.drop_text, state=tk.HIDDEN)

        # 初始化滚动结构（仅首次）
        if not self._scroll_initialized:
            self._init_scroll_structure()
            self._scroll_initialized = True

        # 清空所有子元素
        for child in self.scrollable_frame_inner.winfo_children():
            child.destroy()

        # 重新创建所有预览项
        for i, file_path in enumerate(self.model.dropped_files):
            placeholder = tk.Label(self.scrollable_frame_inner, text=f"⏳⏳⏳ {i + 1}. 加载中...", fg="gray")
            placeholder.pack(fill=tk.X, padx=5, pady=2)
            placeholder._file_path = file_path
            self.create_preview_item(i, file_path, self.scrollable_frame_inner)

        # 更新记录
        self._current_rendered_files = self.model.dropped_files.copy()

        # 刷新滚动区域
        self.canvas.update_idletasks()
        self.canvas.config(scrollregion=self.canvas.bbox("all"))

    def remove_file_by_path(self, file_path):
        """根据文件路径删除文件"""
        if file_path in self.model.dropped_files:
            self.model.dropped_files.remove(file_path)
            # 从 _current_rendered_files 中也移除
            if file_path in self._current_rendered_files:
                self._current_rendered_files.remove(file_path)
            self.update_preview()
        else:
            self.model.logger.info(f"Warning: {file_path} not found in dropped_files")

    def clear_all_files(self):
        self.model.dropped_files.clear()
        self.update_preview()

    def process_files(self):
        try:
            if not self.work_dir.get().strip():
                messagebox.showerror("错误", "请填写台账根目录")
                return
            if not self.folder_name.get().strip():
                messagebox.showerror("错误", "请填写台账文件夹命名")
                return
            if not self.template_path.get().strip():
                messagebox.showerror("错误", "请选择word模板")
                return

            base_dir = self.work_dir.get()
            output_dir = os.path.join(base_dir, self.folder_name.get())
            os.makedirs(output_dir, exist_ok=True)

            # 保存配置
            self.model.save_config(
                self.work_dir.get(),
                self.folder_name.get(),
                self.template_path.get()
            )

            # 处理文件
            img_files = self.controller.process_dropped_files(output_dir)

            if not img_files:
                messagebox.showwarning("警告", "没有找到图片文件")
                self.status_text.set("完成(无图片)")
                return

            # 创建Word文档
            doc_path = self.controller.create_word_document(
                output_dir, img_files,
                self.folder_name.get(),
                self.template_path.get()
            )

            self.status_text.set("处理完成!")
            messagebox.showinfo("完成", f"文件已成功处理并保存到:\n{output_dir}\n\n共处理 {len(img_files)} 张图片")
            os.startfile(output_dir)

            self.model.dropped_files.clear()
            self.update_preview()

        except Exception as e:
            import traceback
            traceback.print_exc()
            self.status_text.set("处理出错")
            messagebox.showerror("错误", f"处理过程中出错:\n{str(e)}")
